Leveraging JDK Tools and Updates to Help Safeguard Java Applications
The JDK comes with a set of built-in tools and capabilities that can help system administrators keep Java installations safe. Although these tools and capabilities are often well known to experienced Java developers, they may not always be familiar to administrators asked to safeguard Java applications.
In this post we’ll go over a few built-in tools and capabilities, providing pointers to further resources. Administrators can explore this information to prepare for situations where they may have to re-evaluate and apply measures to improve the security posture of their Java applications.
In short, this article provides general advice for keeping Java applications safe, rather than advice for mitigating a specific vulnerability.
JDK Command Line Tools for identifying classes being used by an application
When securing a Java application, it is common to first determine whether a particular class is being used. The JDK includes tools that can help with this task.
jcmd tool
The jcmd
tool can be used to retrieve a running application’s system properties
by running jcmd $pid VM.system_properties
, where $pid
stands for the process id of the Java application to be examined.
If using jcmd
you find a potentially interesting library name in a location on the classpath, the library can then be examined further to, for example, determine its version.
In JDK 9 and later versions, jcmd
can print a hierarchical listing of all classes loaded by a Java application running on the same machine,
using the VM.class_hierarchy subcommand, by invoking jcmd $pid VM.class_hierarchy
.
In JDK 7 and later, the GC.class_histogram
subcommand can be used instead to obtain a listing of all instantiated classes (and their memory usage), by invoking jcmd $pid GC.class_histogram
.
Administrators can examine the output from jcmd
for packages of interest, such as com.example.foo.bar
to establish whether a running Java application
has loaded or instantiated classes from packages with those names.
Packages and classes that have not yet been loaded or instantiated would not show up in the output of jcmd
.
In that case, their absence from the jcmd
output alone should not be taken to indicate
that they can’t be loaded by the application at a later point in time, provided that its configuration and dependencies permit that.
With JDK 9 and later, it is not necessary to examine the GC class histogram for a suspect class since a class cannot be instantiated without first being loaded.
You should use the jcmd
version from a JDK that matches the JDK used to run the process you are inspecting.
More information about using the jcmd
tool is available in the JDK troubleshooting guide.
Summary
Command | Supported since | Description | Action |
---|---|---|---|
jcmd <pid> VM.system_properties |
JDK 7 | Prints all the system properties set for the JDK | Examine classpath for specific libraries |
jcmd <pid> GC.class_histogram |
JDK 7 | Creates and prints a class histogram | Examine instantiated classes |
jcmd <pid> VM.class_hierarchy |
JDK 9 | Prints the class hierarchy | Examine loaded classes |
jdeps
Classes and packages may be loaded as dependencies at runtime.
Starting with JDK 8, developers and system administrators can use jdeps
to analyze Java libraries and classes statically
to learn more about their package-level or class-level dependencies.
Developers can use jdeps to examine individual JAR libraries, searching for dependencies on specific packages using the -p
option,
e.g. jdeps -p com.example.foo.bar some.jar
will list the packages in some.jar that depend on the package specified by -p
.
jdeps
can also filter package dependencies using regular expressions.
This allows developers and administrators to search for partial package names to, e.g., find shaded libraries.
More information on using jdeps can be found in the corresponding manual page.
Summary
Command | Supported since | Description | Action |
---|---|---|---|
jdeps -package <package-name> <jar> |
JDK 8 | Statically analyzes Java libraries and classes | Examine package-level or class-level dependencies |
Runtime Configuration Options
System properties, environment variables, and command line options
Another important reason to keep your Java runtimes up to date is because Oracle improves runtime capabilities continuously, and provides mitigations in line with best practices, as documented in the JDK release notes.
Best security practices encourage users to disable capabilities not required by their applications and to configure their systems to restrict required capabilities as much as possible.
For example, several options to disable or restrict the usage of the Java Naming and Directory Interface (JNDI) have been introduced since January 2017 (in 8u121, 7u131, and 6u141), when remote class loading via JNDI object factories was disabled by default. Further restrictions are enabled by default since October 2018 (11.0.1, 8u191, 7u201, and 6u211).
If an application is using JNDI and it does not need factories to create any Java LDAP objects,
administrators should use the JAVA_TOOL_OPTIONS
environment variable at startup to pass the Java runtime command line options:
-Djdk.jndi.object.factoriesFilter=!* and -Dcom.sun.jndi.ldap.object.trustSerialData=false
.
These properties are available on Oracle Java 17, 11.0.11, 8u291, and 7u301 and later (since April 2021).
A detailed description of these properties is available in the Java SE API documentation:
jdk.jndi.object.factoriesFilter
: This system and security property allows a serial filter to be specified that controls the set of object factory classes permitted to instantiate objects from object references returned by naming/directory systems. The factory class named by the reference instance is matched against this filter during remote reference reconstruction. The filter property supports pattern-based filter syntax with the format specified by JEP 290: Filter Incoming Serialization Data. This property applies both to the JNDI/RMI and the JNDI/LDAP built-in provider implementations. The default value allows any object factory class specified in the reference to recreate the referenced object.com.sun.jndi.ldap.object.trustSerialData
: This system property allows control of the deserialization of java objects from thejavaSerializedData
LDAP attribute. To prevent deserialization of java objects from the attribute, the system property can be set to false value. By default, deserialization of java objects from thejavaSerializedData
attribute is allowed.
More information on using the JAVA_TOOL_OPTIONS
environment variable can be found in the corresponding troubleshooting guide.
If your application uses serialization, Oracle recommends using a serialization filter as described in JEP 290: Filter Incoming Serialization Data and JEP 415: Context-Specific Deserialization Filters, to limit the classes that can be deserialized. Filtering incoming streams of object-serialization data significantly improves an application’s security and robustness.
Summary of Configuration Options
Command | Supported since | Description | Action |
---|---|---|---|
jdk.jndi.object.factoriesFilter |
17, 11.0.11, 8u291, and 7u301 | Controls the set of object factory classes permitted to instantiate objects from object references returned by naming/directory systems. | -Djdk.jndi.object.factoriesFilter=!* Disallows use of any object factory for JNDI |
com.sun.jndi.ldap.object.trustSerialData |
17, 11.0.11, 8u291, and 7u301 | Allows control of the deserialization of java objects from the javaSerializedData LDAP attribute. |
-Dcom.sun.jndi.ldap.object.trustSerialData=false Prevents deserialization from the LDAP attribute |
jdk.serialFilter |
9, 8u121, and 7u131 | Filters incoming streams of object-serialization in order to improve both security and robustness. | JDK 21 Guide and Examples JDK 17 Guide and Examples JDK 11 Guide and Examples JDK 7 and 8 Guide and Examples |
Crafting a dedicated runtime to omit unnecessary functionality using jlink
Finally, starting with JDK 9, developers can take advantage of the jlink
tool to generate a custom JDK runtime image for their application, which provides just the JDK functionality required by their code and its dependent libraries.
If an application does not have a GUI, for example, and therefore does not need the desktop APIs and their implementations, a JDK runtime image can be generated that does not contain the java.desktop
module,
reducing the combined download size as well as the potential attack surface for the application.
To continue with our earlier example, if one wanted to create a runtime which did not use JNDI,
one could create a custom runtime that didn’t include the java.naming
module, where that functionality is located.
The set of all modules in a JDK runtime image can be found using the java --list-modules
command.
Developers can create a custom JDK runtime image using jlink
by providing it an explicit list of modules to be contained within the runtime image.
The set of modules to include in an image depends on the requirements and JDK module dependencies of the application the image is being created for.
The latter can be determined using jdeps
.
Starting with JDK 11, jdeps
provides the –print-module-deps
option, which provides a comma-separated list of JDK (and other) modules,
that can be passed to jlink
with the option –add-modules
to create a custom runtime image containing just those modules and their transitive dependencies.
A trivial example using jlink
to generate a custom runtime image containing just a few modules can be found below.
Other custom JDK runtimes may contain a substantially larger subset of the JDK modules, as well as additional files, such as support for different locales, or different service provider implementations, that their code ultimately depends on.
Not all such JDK dependencies can be determined through the static analysis of bytecode performed by jdeps
alone.
Developers should carefully examine their custom JDK runtimes to ensure that all JDK functionality required for their application is present by running java –list-modules
from the generated custom runtime image.
$ jlink --module-path . --add-modules java.base,java.logging,java.net.http,java.xml --output my_runtime
$ my_runtime/bin/java --list-modules
java.base@11.0.13
java.logging@11.0.13
java.net.http@11.0.13
java.xml@11.0.13
Custom JDK runtime images created through jlink
are a well-defined subset of the JDK.
System administrators should apply the same kind of integration testing to custom JDK runtime images as they would to a regular JDK upgrade.
You can learn more about using the jlink
command to create customized JDK runtime images on its manual page.
If creating a custom JDK runtime image is not an option, system administrators can use the –limit-modules
Java option at runtime
to limit the set of JDK modules that can be observed by an application. JDK 9 introduced this Java option as part of JEP 261: Module System.
It allows specifying the precise set of JDK modules, including their dependencies, which will be available to an application at runtime instead of the full Java SE API.
Running code that expects to be able to use APIs on a runtime that doesn’t make them available may lead to runtime exceptions.
When using custom runtimes images generated with jlink
, or if using flags to limit the observability of JDK modules, it is important that developers and administrators run integration testing as when applying a JDK upgrade.
Last review: June 10, 2024